#include "MyMqttClient.h"
#include "string.h"
#include "stdint.h"
#include "stdio.h"
#include <unistd.h>

#define INFO(...)        \
    printf(__VA_ARGS__); \
    printf("\n");
#define ERROR INFO

MyMqttClient::MyMqttClient() {}
MyMqttClient::~MyMqttClient() {}

// init client param
int MyMqttClient::init_params(unsigned long id, char *key, char *addr)
{
    memset(&mp, 0, sizeof(mp));
    snprintf(mp.address, sizeof(mp.address), "%s", addr);
    sprintf(mp.deviceid, "%010lX", id);
    snprintf(mp.key, sizeof(mp.key), "%s", key);

    // subcribe topic
    snprintf(mp.sub_realtime_data_back, sizeof(mp.sub_realtime_data_back), "%010lX_sub/back", id);
    snprintf(mp.sub_control_cmd, sizeof(mp.sub_control_cmd), "%010lX_sub/com", id);
    snprintf(mp.sub_parity_cmd, sizeof(mp.sub_parity_cmd), "$iot/public/com/%010lX", id);

    // publish topic
    snprintf(mp.pub_realtime, sizeof(mp.pub_realtime), "$iot/real/%010lX", id);
    snprintf(mp.pub_control_cmd_back, sizeof(mp.pub_control_cmd_back), "$iot/control/%010lX", id);
    snprintf(mp.pub_parity_cmd_back, sizeof(mp.pub_parity_cmd_back), "$iot/public/comback/%010lX", id);

    // qos
    mp.qos = 1;

    INFO("device-id :%s", mp.deviceid);
    INFO("device-key:%s", mp.key);
    INFO("address   :%s\n", mp.address);
    INFO("sub---realtime-data:%s", mp.sub_realtime_data_back);
    INFO("      control-cmd  :%s", mp.sub_control_cmd);
    INFO("      parity-cmd   :%s\n", mp.sub_parity_cmd);

    INFO("pub---realtime-data:%s", mp.pub_realtime);
    INFO("      control-cmd  :%s", mp.pub_control_cmd_back);
    INFO("      parity-cmd   :%s\n", mp.pub_parity_cmd_back);
    INFO("default qos = %d", mp.qos);

    mp.inited = 1;

    return 0;
}

int MyMqttClient::_sub(const char *sub_topic, int qos, MyMqttClientCallback cb)
{
    int rc = MQTTClient_subscribe(client, sub_topic, qos);
    if (rc != MQTTCLIENT_SUCCESS && rc != qos)
    {
        ERROR("Error %d subscribing to topic %s\n", rc, sub_topic);
        return -1;
    }
    else
    {
        INFO("sub OK, topic=%s", sub_topic);
        if (cb != nullptr)
        {
            cb(this, nullptr, MyMqttClientEvent::SUB_OK);
        }
        return 0;
    }
    return 0;
}

// connect
int MyMqttClient::connect()
{
    MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
    // MQTTClient_SSLOptions ssl_opts = MQTTClient_SSLOptions_initializer;
    int rc = 0;
    if (mp.inited == 0)
    {
        ERROR("MyMqttClient Param is not inited.");
        return -1;
    }

    // 1.init params
    MqttParam *p = &this->mp; // mymqtt_client_param_init(0x01608B0240,(char*)"e567b535",(char*)"tcp://58.42.237.171:1883");

    printf("device-id:%s  key:%s   address:%s\n", p->deviceid, p->key, p->address);

    // 2.init options
    opts.username = p->deviceid;
    opts.password = p->key;

    // 3.use ssl or not
    //  if((strncmp(p->address, "ssl://", 6) == 0 ||
    //  		strncmp(p->address, "wss://", 6) == 0))
    //  		{
    //  			ssl_opts.verify = 1;
    //  			ssl_opts.CApath = opts.capath;
    //  			ssl_opts.keyStore = opts.cert;
    //  			ssl_opts.trustStore = opts.cafile;
    //  			ssl_opts.privateKey = opts.key;
    //  			ssl_opts.privateKeyPassword = opts.keypass;
    //  			ssl_opts.enabledCipherSuites = opts.ciphers;
    //  			opts.ssl = &ssl_opts;
    //  		}

    // 4. connect to mqtt server
    if ((rc = MQTTClient_create(&client, p->address, p->deviceid,
                                MQTTCLIENT_PERSISTENCE_NONE, NULL)) != MQTTCLIENT_SUCCESS)
    {
        printf("Failed to create client, return code %d\n", rc);
        return -1;
    }

    opts.keepAliveInterval = 2; // <=5 s
    opts.cleansession = 1;
    if ((rc = MQTTClient_connect(client, &opts)) != MQTTCLIENT_SUCCESS)
    {
        printf("Failed to connect, return code %d\n", rc);
        return -2;
    }
    if (this->on_connect != nullptr)
    {
        this->on_connect(this, nullptr, MyMqttClientEvent::CONNECT);
    }

    // 5. subcribe topic
    if ((rc = _sub(p->sub_realtime_data_back, p->qos, this->on_sub)) < 0)
        goto CONNECT_ERROR;
    if ((rc = _sub(p->sub_control_cmd, p->qos, this->on_sub)) < 0)
        goto CONNECT_ERROR;
    if ((rc = _sub(p->sub_parity_cmd, p->qos, this->on_sub)) < 0)
        goto CONNECT_ERROR;

    goto CONNECT_OK;

CONNECT_ERROR:
    if (client != NULL)
    {
        this->distroy();
    }
    return rc;

CONNECT_OK:
    return 0;
}

int MyMqttClient::distroy()
{
    if (this->client != nullptr)
    {
        INFO("distroy client:%lx", (long)client);
        MQTTClient_disconnect(client, 1000);
        MQTTClient_destroy(&client);
        client = nullptr;
        return 1;
    }
    return 0;
}

int MyMqttClient::pub(const char *topic, char *payload, int payloadlen)
{
    if (client != nullptr)
    {
        MQTTClient_waitForCompletion(client, last_token, 3000);
        int r = MQTTClient_publish(client, topic, payloadlen, (void *)payload, this->mp.qos, 0, &last_token);
        MQTTClient_waitForCompletion(client, last_token, 3000);
        if (this->on_pub != nullptr)
        {
            MyMqttClientMessage m;
            m.message.payload = payload;
            m.message.payloadlen = payloadlen;
            m.pub_topic = topic;
            m.qos = this->mp.qos;
            m.event = (r == MQTTCLIENT_SUCCESS) ? (MyMqttClientEvent::PUB_MESSAGE_OK) : (MyMqttClientEvent::SUB_MESSAGE_ERR);
            this->on_pub(this, &m, m.event);
        }
        return r;
    }
    else
        return -1;
}

int MyMqttClient::poll()
{

    char *topicName = NULL;
    int topicLen;
    MQTTClient_message *message = NULL;
    int rc = MQTTClient_receive(client, &topicName, &topicLen, &message, 1000);
    if (rc == MQTTCLIENT_DISCONNECTED)
    {
        INFO("mqtt disconnect,it will connect again after 5 seconds");
        if (this->on_disconnect != nullptr)
        {
            this->on_disconnect(this, nullptr, MyMqttClientEvent::DISCONNECT);
        }
        this->distroy();
        sleep(5);
        this->connect();
        return -1;
    }
    else if (message)
    {
        printf("<<<<<:(%d) topic=%s\t  payload=%s\r\n", message->payloadlen, topicName, (char *)message->payload);
        if (this->on_recv != nullptr)
        {
            MyMqttClientMessage m;
            m.message = *message;
            m.event = MyMqttClientEvent::SUB_MESSAGE_OK;
            this->on_recv(this, &m, m.event);
        }
        MQTTClient_freeMessage(&message);
        MQTTClient_free(topicName);
    }
    return 0;
}